package com.starleague.hexagonal.jpa.appengine;

import java.util.Collection;
import java.util.Map;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;

import com.google.common.base.Preconditions;
import com.starleague.hexagonal.HexException;
import com.starleague.hexagonal.HexOutOfBoundsException;
import com.starleague.hexagonal.IHexCoordinate;
import com.starleague.hexagonal.IHexGrid;
import com.starleague.hexagonal.dto.IHexGridDto;
import com.starleague.jpa.appengine.GaeDao;

@MappedSuperclass
public abstract class AbstractHexGridGae
        extends GaeDao
        implements IHexGrid, IHexGridDto
{
    @Basic(optional = false)
    @Column(nullable = false)
    private int minGridX;

    @Basic(optional = false)
    @Column(nullable = false)
    private int minGridY;

    @Basic(optional = false)
    @Column(nullable = false)
    private int numHexesX;

    @Basic(optional = false)
    @Column(nullable = false)
    private int numHexesY;

    @Transient
    private AbstractHexGae[][] hexArray2D;

    protected AbstractHexGridGae(int minGridX,
                                 int minGridY,
                                 int numHexesX,
                                 int numHexesY)
    {
        // TODO: Check these here or in the controller? Write a test for this!
        Preconditions.checkArgument((numHexesX > 0), "Invalid numHexesX [{}]", numHexesX);
        Preconditions.checkArgument((numHexesY > 0), "Invalid numHexesY [{}]", numHexesY);

        this.minGridX = minGridX;
        this.minGridY = minGridY;
        this.numHexesX = numHexesX;
        this.numHexesY = numHexesY;

        this.hexArray2D = new AbstractHexGae[this.numHexesX][this.numHexesY];
    }

    protected AbstractHexGridGae()
    {
        //
    }

    // protected abstract List<? extends HexGae> getHexList();

    @Override
    public int getMinGridX()
    {
        return this.minGridX;
    }

    @Override
    public int getMinGridY()
    {
        return this.minGridY;
    }

    @Override
    public int getNumHexesX()
    {
        return this.numHexesX;
    }

    @Override
    public int getNumHexesY()
    {
        return this.numHexesY;
    }

    @Override
    public AbstractHexGae getHexAt(IHexCoordinate hexCoordinate) throws HexOutOfBoundsException
    {
        int arrayX = hexCoordinate.getGridX() - this.minGridX;
        int arrayY = hexCoordinate.getGridY() - this.minGridY;

        if ((arrayX < 0) || (arrayY < 0) || (arrayX >= this.numHexesX) || (arrayY >= this.numHexesY))
        {
            throw new HexOutOfBoundsException(this, hexCoordinate);
        }

        return this.hexArray2D[arrayX][arrayY];
    }

    // @SuppressWarnings("unchecked")
    // void addHex(HexGae hex) throws HexException
    // {
    // setHex(hex);
    // this.getHexList().add((HEX) hex);
    // }

    protected void setHexes(Collection<? extends AbstractHexGae> hexes) throws HexException
    {
        this.hexArray2D = new AbstractHexGae[this.numHexesX][this.numHexesY];
        for (AbstractHexGae hex : hexes)
        {
            setHex(hex);
        }
    }

    protected void setHex(AbstractHexGae hex) throws HexException
    {
        IHexCoordinate hexCoordinate = hex.getHexCoordinate();

        int arrayX = hexCoordinate.getGridX() - this.minGridX;
        int arrayY = hexCoordinate.getGridY() - this.minGridY;

        if ((arrayX < 0) || (arrayY < 0) || (arrayX >= this.numHexesX) || (arrayY >= this.numHexesY))
        {
            throw new HexOutOfBoundsException(this, hexCoordinate);
        }

        // Make sure the hex at the given location doesn't already exist.
        AbstractHexGae oldHex = this.hexArray2D[arrayX][arrayY];
        // if ((oldHex != null) && (oldHex != hex))
        if (oldHex != null)
        {
            throw new HexException("[" + hexCoordinate + "] already has a Hex:" + oldHex);
        }

        this.hexArray2D[arrayX][arrayY] = hex;
    }

    // @PostLoad
    // public void postLoad() throws HexException
    // {
    // setHexes(this.getHexList());
    // }
    //
    // // This may seem unnecessary, but it is actually very important
    // @PostUpdate
    // public void postUpdate() throws HexException
    // {
    // setHexes(this.getHexList());
    // }

    @Override
    public Map<String, Object> infoProperties()
    {
        Map<String, Object> infoPropertyMap = super.infoProperties();
        infoPropertyMap.put("minGridX", this.minGridX);
        infoPropertyMap.put("minGridY", this.minGridY);
        infoPropertyMap.put("numHexesX", this.numHexesX);
        infoPropertyMap.put("numHexesY", this.numHexesY);
        return infoPropertyMap;
    }
}
